package com.secure.handler;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.secure.annotation.ApiSecure;
import com.secure.bean.AppKey;
import com.secure.bean.SecureRequest;
import com.secure.bean.UserKey;
import com.secure.constant.RequestConstant;
import com.secure.exception.ApiSecureException;
import com.secure.properties.SecureProperties;
import com.secure.util.PathUtil;
import com.secure.util.RequestUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RequestBody;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;

@RequiredArgsConstructor
public class SecureRequestHandler {

    private final ObjectMapper objectMapper;
    private final SecureProperties secureProperties;
    private final SignatureHandler signatureHandler;
    private final EncryptDecryptHandler encryptDecryptHandler;

    /**
     * 是否需要安全请求
     * 两个条件：
     * 1，请求体传参，方法入参注解@RequestBody
     * 2，方法注解ApiSecure或者配置文件配置api.secure.handlerMappingPathList
     *
     * @param method 方法
     * @return
     */
    public boolean isNeedSecureRequestForBody(Method method) {
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        return (method.isAnnotationPresent(ApiSecure.class) || PathUtil.isMatch(secureProperties.getHandlerMappingPathList(), RequestUtil.getCurrentRequestServletPath(), false)) && Arrays.stream(parameterAnnotations).anyMatch(annotations -> Arrays.stream(annotations).anyMatch(annotation -> annotation.annotationType().equals(RequestBody.class)));
    }

    /**
     * 是否需要安全请求
     * 两个条件：
     * 1，非请求体传参
     * 2，方法注解ApiSecure或者配置文件配置api.secure.handlerMappingPathList
     *
     * @param method 方法
     * @return
     */
    public boolean isNeedSecureRequestForParameter(Method method) {
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        return (method.isAnnotationPresent(ApiSecure.class) || PathUtil.isMatch(secureProperties.getHandlerMappingPathList(), RequestUtil.getCurrentRequestServletPath(), false)) && Arrays.stream(parameterAnnotations).noneMatch(annotations -> Arrays.stream(annotations).anyMatch(annotation -> annotation.annotationType().equals(RequestBody.class)));
    }

    /**
     * 解密&验签请求
     *
     * @param secureRequest 解密&验签请求
     * @return 请求参数值
     */
    public String decryptAndVerifySignatureRequest(SecureRequest secureRequest) throws JsonProcessingException {
        //检查请求参数和安全配置
        validateRequestParams(secureRequest);
        UserKey userKey = getUserKey(secureRequest);
        validateSecureConfig(secureRequest, userKey);

        //解密
        String plainText = encryptDecryptHandler.decryptByPrivateKey(secureRequest.getData(), secureProperties.getAppKey().getEncryptDecrypt().getPrivateKey(), String.class);

        //验签
        Map<String, Object> plainMap = objectMapper.readValue(plainText, new TypeReference<Map<String, Object>>() {
        });
        Object signature = plainMap.get(RequestConstant.SIGNATURE);
        if (ObjectUtil.isNull(signature)) {
            throw new ApiSecureException("请求签名不能为空");
        }
        plainMap.remove(RequestConstant.SIGNATURE);
        boolean verify = signatureHandler.verify(plainMap, signature.toString(), userKey.getSignature().getPublicKey());
        if (!verify) {
            throw new ApiSecureException("请求验签失败");
        }
        return plainText;
    }

    /**
     * 校验请求参数
     *
     * @param secureRequest
     */
    private void validateRequestParams(SecureRequest secureRequest) {
        if (StrUtil.isBlank(secureRequest.getUserId())) {
            throw new ApiSecureException("消费方：消费方标识不能为空");
        }
        if (StrUtil.isBlank(secureRequest.getAppId())) {
            throw new ApiSecureException("消费方：服务方标识不能为空");
        }
        if (StrUtil.isBlank(secureRequest.getData())) {
            throw new ApiSecureException("消费方：请求数据不能为空");
        }
        int timeout = secureProperties.getTimeout();
        if (timeout > 0) {
            if (ObjectUtil.isNull(secureRequest.getTimestamp())) {
                throw new ApiSecureException("消费方：请求时间戳不能为空");
            }
            Instant requestInstant = Instant.ofEpochSecond(secureRequest.getTimestamp());
            Instant currentInstant = Instant.now();
            if (requestInstant.isAfter(currentInstant)) {
                throw new ApiSecureException("消费方：请求时间戳异常");
            }
            Duration duration = Duration.between(requestInstant, currentInstant);
            if (duration.getSeconds() > timeout) {
                throw new ApiSecureException("消费方：请求超时");
            }
        }
    }

    /**
     * 获取用户密钥配置
     *
     * @param secureRequest 安全请求对象
     * @return
     */
    private UserKey getUserKey(SecureRequest secureRequest) {
        Optional<UserKey> userKeyOptional = secureProperties.getUserKeyConfig().entrySet().stream().filter(entry -> secureRequest.getUserId().equals(entry.getValue().getUserId())).map(Map.Entry::getValue).findFirst();
        return userKeyOptional.orElse(null);
    }

    /**
     * 校验应用密钥配置情况
     *
     * @param userKey 服务消费端密钥
     * @return
     */
    private void validateSecureConfig(SecureRequest secureRequest, UserKey userKey) {
        AppKey appKey = secureProperties.getAppKey();
        if (ObjectUtil.isNull(appKey)) {
            throw new ApiSecureException("服务方：服务方安全配置未配置");
        }
        if (StrUtil.isBlank(appKey.getAppId())) {
            throw new ApiSecureException("服务方：服务方标识未配置");
        } else if (!secureRequest.getAppId().equals(appKey.getAppId())) {
            throw new ApiSecureException("消费方：服务方标识不正确");
        }
        if (ObjectUtil.isNull(appKey.getEncryptDecrypt()) || StrUtil.isBlank(appKey.getEncryptDecrypt().getPrivateKey())) {
            throw new ApiSecureException("服务方：解密请求/加密响应的私钥未配置");
        }
        Optional<UserKey> userKeyOptional = secureProperties.getUserKeyConfig().entrySet().stream().filter(entry -> secureRequest.getUserId().equals(entry.getValue().getUserId())).map(Map.Entry::getValue).findFirst();
        if (!userKeyOptional.isPresent()) {
            throw new ApiSecureException("服务方：消费方安全配置未配置");
        }
        if (ObjectUtil.isNull(userKey.getSignature()) || StrUtil.isBlank(userKey.getSignature().getPublicKey())) {
            throw new ApiSecureException("服务方：消费方提供的验签公钥未配置");
        }
    }
}
